/*
 * msgstat.c
 * 
 * Copyright 2012 Kamil Cukrowski <kamil@dyzio.pl>
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 * 
 */
#define MSGSTAT_VERSION "2.1.1"

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sysinfo.h>
#include <fcntl.h>
#include <syslog.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <arpa/inet.h>
#include <stdarg.h>
#include <msgserv.h>
#include <common.h>

#define MY_NAME "msgstat"
#define PID_FILE "/tmp/msgstat.pid"
#define CPUNUM 4

in_addr_t server_ip = { 16777343 }; // 127.0.0.1
int debug = 0;
int PID;
int sockfd;
int refresh_time = 3;

struct cpu_use_type {
  int use, total;
};

struct net_use_type {
  unsigned long long rx;
  unsigned long long tx;
};


#define fatal(format, ...) do { PERROR(format, ##__VA_ARGS__); safe_exit(); } while(0)
void safe_exit() 
{
	msglcd_disconnect(sockfd);
	exit(0);
}
void sig_hup (int param) {
	PDEBUG("Signal received. Going down");
	safe_exit();
}

static int __attribute__((format (printf, 3, 4))) 
	mysnprintf (char *s, int n, const char *format, ...)
{
	/* its same as snprintf
	 * but does not write '\0' on the end of the string
	 * and returns number of chars *written*
	 * i dont even care about checking - be carefull */
	char msg[256];
	short int i;
	
	va_list va;
	va_start (va, format);
	vsnprintf (msg, sizeof(msg), format, va);
	msg[sizeof(msg) - 1] = 0;
	va_end (va);
	
	for (i=0; i < n && i < 256 && i < strlen(msg); ++i) {
		s[i] = msg[i];
	}
	return(i);
}

void parse_cmd(int argc, char *argv[])
{
	int c=0;
	opterr=0;
	while ( (c = getopt(argc, argv, "hdr:i:")) != -1 )
	switch (c) {
	case 'h':
		printf("MsgLCD: messaging system to LCD geteway.\n"
		"\t-h\tdisplay this help.\n"
		"\t-d\tdebug mode on (do not fork into the background);\n"
		"\t-r\tset refresh time in secends.\n"
		"\t-i<IP address>\t server ip address.\n");
		exit(0);
	case 'd':
		debug=1;
		break;
	case 'r':
		refresh_time=atoi(optarg);
		break;
	case 'i':
		server_ip=inet_addr(optarg);
		break;
	}
}


float get_temp (int number) 
{
	FILE *file_fd = NULL;
	char buff[8];
	
	/*
	 * hwmon2 -> number == {1,2,3} - motherboard tempn
	 * hwmon1 -> number == 4 - cpu temp
	 * hwmon0 -> number == 5 - gpu temp
	 */
	
	switch(number) {
		case 1:
			file_fd = fopen("/sys/devices/platform/it87.552/temp1_input","r");
			break;
		case 2:
			file_fd = fopen("/sys/devices/platform/it87.552/temp2_input","r");
			break;
		case 3:
			file_fd = fopen("/sys/devices/platform/it87.552/temp3_input","r");
			break;
		case 4:
			file_fd = fopen("/sys/devices/pci0000:00/0000:00:18.3/temp1_input","r");
			break;
		case 5:
			do { /* theres something cracked with my head */
				const char buff[3][75] = { 
					"/sys/devices/pci0000:00/0000:00:02.0/0000:01:00.0/hwmon/hwmon0/temp1_input" ,
					"/sys/devices/pci0000:00/0000:00:02.0/0000:01:00.0/hwmon/hwmon1/temp1_input" ,
					"/sys/devices/pci0000:00/0000:00:02.0/0000:01:00.0/hwmon/hwmon2/temp1_input" ,
				};
				file_fd = fopen(buff[0], "r");
				if ( !file_fd ) {
					file_fd = fopen(buff[1], "r");
					if ( !file_fd ) {
						file_fd = fopen(buff[2], "r");
					}
				}
			} while(0);
			break;
		default:
			file_fd = NULL;
	}
	if ( !file_fd ) {
		PDEBUG("error opening file: %d %s.", errno, strerror(errno));
		return -errno;
	}
	fgets(buff, 8, file_fd);
	fclose(file_fd);
	return ((float)atoi(buff)/(float)1000);
}

static void get_cpu_usage(float *cpuuse) 
{
	static struct cpu_use_type cpuuse_old[CPUNUM] = { [0 ... CPUNUM-1] = {0,0} };
	int i=0;
	char buff[256];
	FILE *stat_fd;
	
	struct {
		long int user, nice, sys, idle, iowait, hirq, sirq, use, hgw;
	} cpu_usage;
	
	if ( (stat_fd = fopen("/proc/stat","r")) == NULL ) {
		PDEBUG("/proc/stat err_fopen\n");
		return;
	}
	for (i=0; ( i<CPUNUM ) && ( fgets(buff,256,stat_fd) != NULL ) &&
		( sscanf(buff+5,"%ld %ld %ld %ld %ld %ld %ld %ld\n",
			&cpu_usage.user,
			&cpu_usage.nice,
			&cpu_usage.sys,
			&cpu_usage.idle,
			&cpu_usage.iowait,
			&cpu_usage.hirq,
			&cpu_usage.sirq,
			&cpu_usage.hgw) == 8 ) ; 
			i++ ) {
		const int use = cpu_usage.user+cpu_usage.nice+cpu_usage.sys+cpu_usage.hirq+cpu_usage.sirq;
		const int total = cpu_usage.idle+cpu_usage.iowait+cpu_usage.user+cpu_usage.nice+cpu_usage.sys+cpu_usage.hirq+cpu_usage.sirq;
		
		if ( total == cpuuse_old[i].total ) // avoid dividing throu zero
			cpuuse[i]=0;
		else
			cpuuse[i]=(float)((use-cpuuse_old[i].use)*100) / (float)( total-cpuuse_old[i].total);
		cpuuse_old[i].use=use;
		cpuuse_old[i].total=total;
	
	}
	fclose(stat_fd);
}

static const char *strfindstr(const char *str1, const char *str2) 
{
	const int len2 = strlen(str2);
	const int len = strlen(str1) - len2;
	int i;
	
	for (i = 0; i < len; ++i)
            if ( !strncmp(str1 + i, str2, len2) )
		return str1+i;
	return NULL;

}

static int get_net_use(struct net_use_type *netuse, char *dev)
{
	char buff[1024];
	FILE *filefd;
	unsigned long int tmp;
	
	filefd = fopen("/proc/net/dev","r");
	if ( !filefd ) 
		goto err_fopen;
	
	while ( fgets(buff, 1024, filefd) )
		if ( strfindstr(buff, dev) )
			break;
	if ( feof(filefd) )
		goto err_dev_not_found;
	fclose(filefd);
	
	sscanf(buff+7,
		"%llu %lu %lu %lu %lu %lu %lu %lu %llu %lu %lu %lu %lu %lu %lu %lu",
		&(*netuse).rx, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp,
		&(*netuse).tx, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp, &tmp);
		
	return 0;
	
err_dev_not_found:
	PDEBUG("err_dev_not_found \"%s\"", dev);
	(*netuse).rx = 9999;
	(*netuse).tx = -9999;
	fclose(filefd);
	return -1;
err_fopen:
	PDEBUG("err_fopep \"%s\"", dev);
	(*netuse).rx = 999999;
	(*netuse).tx = -999999;
	return -99999;
}

static void readable_net_size(char *buff, unsigned long long n)
{
	n/=refresh_time;                                                                                                      
	if ( n < 1024 ) // 1 - 1024                                                                                           
		mysnprintf(buff, 5, "%4llub", n);
	else if ( n < 1024*10 ) // 1K - 10K                                                                                     
		mysnprintf(buff, 5, "%1.2fK", (float)n / (float)1024);
	else if ( n < 1024*100 ) // 10K - 100K                                                                                  
		mysnprintf(buff, 5, "%2.1fK", (float)n / (float)1024);
	else if ( n < 1024*1000 ) // 100K - 1024K                                                                               
		mysnprintf(buff, 5, "%4lluK", n / 1024);                                                                        
	else if ( n < 1024*1024 ) // 1M - 10M                                                                                  
		mysnprintf(buff, 5, " %1.1fM", (float)n / (float)1048576);
	else if ( n < 1024*1024*10 ) // 10M - 100M , 1048576 = 1024^2
		mysnprintf(buff, 5, "%2.1fM", (float)n / (float)1048576);
	else if ( n < 1024*1024*100 ) // 100M - 1024M , 1073741824 = 1024^3
		mysnprintf(buff, 5, "%4lluM", n / 1048576 );
	else
		mysnprintf(buff, 5, "%4lluGb", n / 1073741824 );
}

static void readable_cpu_usage(char *where, float n) 
{
	if ( n < 10 )
		mysnprintf(where, 3, "%1.1f", n);
	else 
		mysnprintf(where, 3, "%3.0f", n);
}

static void readable_mem_size(char *buff, unsigned long long n)
{                                      
	
	if ( n < 1024 )
		mysnprintf(buff, 7, "%6lluK", n);
	else if ( n < 1024*1024*100 )
		mysnprintf(buff, 7, "%6lluM", n/1024);
	else
		mysnprintf(buff, 7, "%6lluG", n/(1024*1024));
}

static void meminfo_write(char *buff)
{
	FILE *fp;
	char buf[256];
	unsigned long int kb_main_total, kb_main_free, kb_main_cached, kb_main_buffers;
	unsigned long int SwapTotal, SwapFree;
	int i;

	fp = fopen("/proc/meminfo", "r");
	if ( fp == NULL ) {
		PDEBUG("huj %d: %s ", errno, strerror(errno));
		return;
	}
	fgets(buf, 256, fp);
	sscanf(strchr(buf, ':'), ": %lu", &kb_main_total);
	fgets(buf, 256, fp);
	sscanf(strchr(buf, ':'), ": %lu", &kb_main_free);
	fgets(buf, 256, fp);
	sscanf(strchr(buf, ':'), ": %lu", &kb_main_buffers);
	fgets(buf, 256, fp);
	sscanf(strchr(buf, ':'), ": %lu", &kb_main_cached);
	
	for(i=0; i<9; i++)
		fgets(buf, 256, fp);
	
	fgets(buf, 256, fp);
	sscanf(strchr(buf, ':'), ": %lu", &SwapTotal);
	fgets(buf, 256, fp);
	sscanf(strchr(buf, ':'), ": %lu", &SwapFree);
	
	fclose(fp);
	mysnprintf(buff, 6, "%4dM/", (int)((kb_main_total - kb_main_free - kb_main_cached - kb_main_buffers)/1024) );
	readable_mem_size(buff+6, SwapTotal-SwapFree);
}

static void handle_msglcd_handle_server(const int lcd_sock) 
{
	int i;
	switch ( (i = msglcd_handle_server(lcd_sock)) ) {
	case -5:
	case -1:
		fatal("Strange error occured %d ", i);
	case -4:
		fatal("Server closed the connection. %d ", i);
	case -6:
		fatal("server sended error messege %d", i);
	case -2:
		fatal("Server closed the connection without sending exit mesg. %d ", i);
	case -3:
		fatal("my mesg has somth wrong %d ", i);
	}
}

static void handle_msg_updat(const int sockfd, const char nr, const char pri, const char *mesg, const int len) 
{
	int i;
	i = msglcd_updat(sockfd, nr, pri, mesg, len);
	if ( i == 0 ) return;
	fatal("could not update mesg. msglcd_updat returned: %d", i);
}

int main(int argc, char **argv) 
{
	struct net_use_type net_old[2] = { [0 ... 1] = {0,0} };
	
	char buff[4][41] = {
		[0] = "                               up   :   ",
		[1] = "CPU:    %    %    %    %     M/         ",
		[2] = "                                        ", //"    CPU:  C MB:  C CPU:  C CORE:  C     ",
		[3] = "leonidas eth0 in:       out:            ",
	};
	
	parse_cmd(argc, argv);

	if (!server_ip) {
		fprintf(stderr, "Please give msgsrv ip address.\n");
		safe_exit();
	}
	
	signal(SIGHUP,sig_hup);
	signal(SIGQUIT,sig_hup);
	signal(SIGTERM,sig_hup);
	signal(SIGINT,sig_hup);
	
	sockfd = msglcd_connect(server_ip);
	if (sockfd < 0) fatal("Could not connect to lcd server %d \n", sockfd);

	if ( !debug ) { 
		openlog(MY_NAME,LOG_PID,LOG_DAEMON);
		syslog(LOG_INFO,"Startup  succeeded.");
	}
	

	PDEBUG("Connected with %s.\n", inet_ntoa(*(struct in_addr *)&server_ip));
  
	for (;;) {
		int i;
		
		
		/* first line, write time */
		{ /* date | centereed int the first 30 chars */
		const time_t CurentTime = time(NULL);
		int len;
		len = strftime(buff[0], 31, "%H:%M %A %d/%m/%y", localtime(&CurentTime));
		if ( !len ) 
			fatal("strftime");
		if ( len%2 ) {
			len/=2;
			for(i = 30; i >= 15+len+1; --i) /* clean before date */
				buff[0][i] = ' ';
			for(; i >= 15-len; --i) /* move the date from the beggining to the middle */
				buff[0][i] = buff[0][i - 15 + len];
			for(;i>=0; --i) /* clean the beggining */
				buff[0][i] = ' ';
		} else {
			len/=2;
			for (i = 30; i >= 15+len; --i) /* clean before date */
				buff[0][i] = ' ';
			for (; i >= 15-len ; --i) /* move the date from the beggining to the middle */
				buff[0][i] = buff[0][i - 15 + len];
			for (;i>=0; --i) /* clean the beggining */
				buff[0][i] = ' ';
		}
		}
		
		{ /* uptime */
		/* get sysinfo info */
		struct sysinfo sys_info;
		if (sysinfo(&sys_info) != 0) fatal("sysinfo");
		int hours = sys_info.uptime / 3600;
		mysnprintf(buff[0]+33, 3, "%3d", hours);
		mysnprintf(buff[0]+37, 2, "%2ld", (sys_info.uptime / 60) - (hours * 60));
		}
		
		/* second line */
		{
		float cpuuse[CPUNUM];
		/* get cpu usage */
		get_cpu_usage(cpuuse);
		for (i = 0; i < CPUNUM; i++) {
			readable_cpu_usage(buff[1]+5*i+5, cpuuse[i]);
		}
		}
		/* write total used ram */
		meminfo_write(buff[1]+26);
		
		/* third line, temperatures */
		mysnprintf(buff[2]+3, 37, "CPU:%2.0f NB:%2.0f SB:%2.0f CPU:%2.0f GPU:%2.0f",
			get_temp(1),get_temp(2),get_temp(3),get_temp(4),get_temp(5));
		
		/* forth line, write bandwitch speed */
		{
		struct net_use_type net_new[2];
		mysnprintf(buff[3], 39, "eth0  >      <       |ppp0  >     <     ");
		
		/* GET bandwitch rx and tx speed */
		get_net_use(&net_new[0], "eth0");
		readable_net_size(buff[3]+8, net_new[0].rx-net_old[0].rx);
		readable_net_size(buff[3]+15, net_new[0].tx-net_old[0].tx);
		net_old[0] = net_new[0];
		
		if ( !get_net_use(&net_new[1], "ppp0") ) {
			readable_net_size(buff[3]+29, net_new[1].rx-net_old[1].rx);
			readable_net_size(buff[3]+35, net_new[1].tx-net_old[1].tx);
			net_old[1] = net_new[1];
		} else {
			net_old[1].tx = 0;
			net_old[1].rx = 0;
			mysnprintf(buff[3]+22, 20, "ppp0 not connected");
		}
		}
		
		/*write  when debug, buffers on the screen*/
		if ( debug ) {
			int j;
			for (i=0; i < ((sizeof(buff)/sizeof(*buff)/sizeof(**buff))); i++) {
				for (j = 0; j < 40; j++)
					printf("%c", buff[i][j]);
				printf("\n");
			}
			printf("\n");
		}
		
		/* send the message */
		handle_msg_updat(sockfd, 0, MSG_INFO, buff[0], 40);
		handle_msg_updat(sockfd, 1, MSG_INFO, buff[1], 40);
		handle_msg_updat(sockfd, 2, MSG_INFO, buff[2], 40);
		handle_msg_updat(sockfd, 3, MSG_INFO, buff[3], 40);
		/* handle server messages */
		handle_msglcd_handle_server(sockfd);
		
		sleep(refresh_time);
	}
	return(0);
}

